package fanalg;

import java.util.ArrayList;
import java.util.List;

/**
 * Class to represent logic gates
 * @author Tomasz Werszko
 *
 */
public class Gate extends CircuitEntity{
	/**
	 * Stores truth vector of the gate
	 */
	public boolean[] truthVector;
	
	//protected
	
	/**
	 * Constructor which takes array of 2 inputs and name of output
	 */
	Gate(String[] inputs, String output){
		if(inputs.length >= 1){
			this.inputs = new String[inputs.length];
			this.inValues = new LogicSystem[inputs.length];
			for(int i = 0; i < inputs.length; i++){
				this.inputs[i] = inputs[i];
				this.inValues[i] = LogicSystem.X;
			}
		}
		this.output = output;
		this.outValue = LogicSystem.X;
		this.truthVector = new boolean[(int) Math.pow(2, inputs.length)];
		for(int i = 0; i < this.truthVector.length; i++){
			this.truthVector[i] = false;
		}
	}
	public String toString(){
		String string = "{Gate: " + super.toString();
		for(int i = 0; i< this.truthVector.length; i++){
			string += " " + this.truthVector[i];
		}
		 string += "}";
		return  string;
	}
	
	/**
	 * Returns list of combinations of variables needed to be set to get 0/1 on the output
	 * @param varCombinations 	- returns a list of combinations of variables
	 * @param varStates			- returns states paired with varCombinations
	 * @param cc				- controllability
	 */
	void getCCInputs(List<List<String>> varCombinations, List<List<Integer>> varStates, boolean cc){
		for(int i = 0; i < this.truthVector.length; i++){
			ArrayList<String> list = new ArrayList<String>();
			ArrayList<Integer> values = new ArrayList<Integer>();
			String binary = Integer.toBinaryString(i);
			
			int j = 0;
			while(j < this.truthVector.length){
				if(Math.pow(2, j) >= this.truthVector.length){
					break;
				}
				
				int bigger = (int)Math.pow(2, j);
				//System.out.println(j + " " + binary.length() + " " + binary);
				
				if(binary.length() > j && binary.charAt(binary.length() - 1 -j) == '1'){
					if((i - bigger <  0) || (i - bigger >= 0 && this.truthVector[i] == cc && this.truthVector[i] != this.truthVector[i - bigger])){
						//System.out.println(binary + " " + j);
						list.add(this.inputs[j]);
						values.add(new Integer(Integer.parseInt(Character.toString(binary.charAt(binary.length() - 1 -j)))));
					}
					
				}else{
					if((i + bigger >= this.truthVector.length) || (i + bigger < this.truthVector.length && this.truthVector[i] == cc && this.truthVector[i] != this.truthVector[i + bigger])){
						//System.out.println(binary + " " + j);
						list.add(this.inputs[j]);
						values.add(new Integer(0));
					}
				}
				j++;
			}
			
			if(list.size() > 0){
				varCombinations.add(list);
				varStates.add(values);
			}
		}
	}
	public LogicSystem evaluate(LogicSystem[] inputs){
		ArrayList<String> combinations = new ArrayList<String>();
		ArrayList<String> combinationsC = new ArrayList<String>();
		int countx = 0;
		int countd = 0;
		for(int i = 0; i < inputs.length; i++){
			if(inputs[i] == LogicSystem.X){
				countx++;
			}else if(inputs[i] == LogicSystem.D || inputs[i] == LogicSystem.notD){
				countd++;
			}
		}
		
		for(int i = 0; i < Math.pow(2, (double)countx); i++){
			combinations.add(new String());
			combinationsC.add(new String());
		}
		
		for(int i = 0; i < inputs.length; i++){		
			int j = 0;
			for(int z = 0; z < combinations.size(); z++){
				String combination = combinations.get(z);
				String combinationC = combinationsC.get(z);
				if(inputs[i] == LogicSystem.TRUE){
					combination += "1";
					combinationC += "1";
				}else if(inputs[i] == LogicSystem.FALSE){
					combination += "0";
					combinationC += "0";
				}else if(inputs[i] == LogicSystem.D){
					combination += "0";
					combinationC += "1";
				}
				else if(inputs[i] == LogicSystem.notD){
					combination += "1";
					combinationC += "0";
				}else{
					combination += new Integer(j%2).toString();
					combinationC += new Integer(j%2).toString(); 
				}
				j++;
				combinations.remove(z);
				combinations.add(z, combination);
				combinationsC.remove(z);
				combinationsC.add(z, combinationC);
			}
		}
		ArrayList<Integer> vectors = new ArrayList<Integer>();
		for(String combination : combinations){
			vectors.add(new Integer(Integer.parseInt(combination, 2)));
		}
		
		boolean previous = truthVector[vectors.get(0)];
		boolean current = false;
		for(Integer vector : vectors){
			current = truthVector[vector];
			if(current != previous){
				return LogicSystem.X;
			}
		}
		
		if(countd > 0){
			ArrayList<Integer> vectorsC = new ArrayList<Integer>();
			for(String combinationC : combinationsC){
				vectorsC.add(new Integer(Integer.parseInt(combinationC, 2)));
			}
			boolean previousC = truthVector[vectorsC.get(0)];
			boolean currentC = false;
			for(Integer vectorC : vectorsC){
				currentC = truthVector[vectorC];
				if(currentC != previousC){
					return LogicSystem.X;
				}
			}
			if(current == true && currentC == false){
				return LogicSystem.notD;
			}else if(current == false && currentC == true){
				return LogicSystem.D;
			}
		}
		
		if(current == true){
			return LogicSystem.TRUE;
		}else{
			return LogicSystem.FALSE;
		}
	}

}
